﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Web;
using System.Web.Mvc;
using Mindfor.Web.Config;
using Mindfor.Web.Data;
using Mindfor.Web.Routing;
using NHibernate;
using NHibernate.Linq;
using System.Drawing.Imaging;

namespace Mindfor.Web.Modules.System.Controllers
{
	/// <summary>
	/// Controller for managing files and images.
	/// </summary>
	public class FilesController : CmsController
	{
		/// <summary>
		/// Gets or sets function to check file read access.
		/// </summary>
		public static Func<File, bool> HasFileReadAccessFunc { get; set; }

		/// <summary>
		/// Gets or sets function to check file delete access.
		/// </summary>
		public static Func<File, bool> HasFileDeleteAccessFunc { get; set; }

		/// <summary>
		/// Gets or sets function to check file upload access.
		/// </summary>
		public static Func<Folder, bool> HasFileUploadAccessFunc { get; set; }

		/// <summary>
		/// Gets or sets function to check folder listing access.
		/// </summary>
		public static Func<Folder, bool> HasFolderListAccessFunc { get; set; }

		/// <summary>
		/// Temporary directory for file uploading.
		/// </summary>
		public const string TempDirectory = "~/App_Data/Temp";

		/// <summary>
		/// Extensions of image files.
		/// </summary>
		public static readonly string[] ImageExtensionsList = new string[] { "png", "jpg", "jpeg", "bmp", "gif" };

		#region Download

		/// <summary>
		/// Returns FileResult which sends file to response.
		/// </summary>
		/// <param name="id">Db id of the file.</param>
		/// <param name="ext">File extension.</param>
		[UrlRoute("", Name="File", Path="Files/{id}-{name}.{ext}")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public virtual ActionResult File(int id)
		{
			File file = Data.Get<File>(id);
			if (file == null)
				return HttpNotFound();
			else if (HasFileReadAccessFunc != null && !HasFileReadAccessFunc(file))
				return HttpAccessDenied();

			string contentType = MimeTypes.GetByExtension(file.Extension);
			return File(file.GetData(), contentType);
		}

		/// <summary>
		/// Returns FileResult which sends image to response.
		/// </summary>
		/// <param name="id">Db file id.</param>
		/// <param name="ext">Image extension.</param>
		/// <param name="size">Maximum image size.</param>
		[UrlRoute("", Name="Image", Path="Images/{id}.{ext}/{size}")]
		[UrlRouteDefault("size")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public virtual ActionResult Image(int id, string ext, int? size)
		{
			// load image
			File file = Data.Get<File>(id);
			if (file == null)
				return HttpNotFound();
			else if (HasFileReadAccessFunc != null && !HasFileReadAccessFunc(file))
				return HttpAccessDenied();

			var s = file.GetData();
			if (s == null)
				return null;
			global::System.Drawing.Image image;
			try
			{
				image = global::System.Drawing.Image.FromStream(s);
			}
			catch (Exception ex)
			{
				throw new FormatException("Can not recognize image format.", ex);
			}
			
			// return plain if no resize
			if (size == null)
			{
				string contentType = null;
				if (image.RawFormat.Guid == ImageFormat.Png.Guid)
					contentType = "image/png";
				else if (image.RawFormat.Guid == ImageFormat.Jpeg.Guid)
					contentType = "image/jpeg";
				else if (image.RawFormat.Guid == ImageFormat.Bmp.Guid)
					contentType = "image/bmp";
				else if (image.RawFormat.Guid == ImageFormat.Gif.Guid)
					contentType = "image/gif";

				s.Seek(0, global::System.IO.SeekOrigin.Begin);
				return File(s, contentType);
			}
			// resize
			else
			{
				return Image(image, null, size.Value, size.Value);
			}
		}

		[UrlRoute("", Name="ImageEntity", Path="Images/{typeName}/{id}/{fieldName}/{size}")]
		[UrlRouteDefault("size")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public virtual ActionResult ImageEntity(int id, string typeName, string fieldName, int? size)
		{
			// get type
			Type type;
			try
			{
				type = TypeFinder.GetTypeFromCurrentDomain(typeName);
			}
			catch
			{
				return HttpNotFound(String.Format("Cannot load entity type \"{0}\".", typeName));
			}
			if (type == null)
				return HttpNotFound(String.Format("Entity type \"{0}\" not found.", typeName));

			// load entity
			object entity;
			try
			{
				entity = Data.Get(type, id);
			}
			catch
			{
				return HttpNotFound(String.Format("Cannot load entity \"{0}\".", typeName));
			}
			if (entity == null)
				return HttpNotFound(String.Format("Entity \"{0}\" not found.", typeName));

			// load image
			byte[] bytes = entity.GetPropertyValue(fieldName) as byte[];
			if (bytes == null)
				return HttpNotFound(String.Format("Field \"{0}\" not found.", fieldName));
			var s = new global::System.IO.MemoryStream(bytes);
			s.Seek(0, global::System.IO.SeekOrigin.Begin);
			global::System.Drawing.Image image;
			try
			{
				image = global::System.Drawing.Image.FromStream(s);
			}
			catch (Exception ex)
			{
				throw new FormatException("Can not recognize image format.", ex);
			}
			
			// return plain if no resize
			if (size == null)
			{
				string contentType = null;
				if (image.RawFormat.Guid == ImageFormat.Png.Guid)
					contentType = "image/png";
				else if (image.RawFormat.Guid == ImageFormat.Jpeg.Guid)
					contentType = "image/jpeg";
				else if (image.RawFormat.Guid == ImageFormat.Bmp.Guid)
					contentType = "image/bmp";
				else if (image.RawFormat.Guid == ImageFormat.Gif.Guid)
					contentType = "image/gif";

				s.Seek(0, global::System.IO.SeekOrigin.Begin);
				return File(s, contentType);
			}
			// resize
			else
			{
				return Image(image, null, size.Value, size.Value);
			}
		}

		#endregion

		#region Manage

		/// <summary>
		/// Displays the list of images and files.
		/// </summary>
		[CmsAuthorize]
		[UrlRoute("", Path="Admin/Files")]
		public ActionResult Index(int? folderId, bool? showInsertLinks)
		{
			Folder folder = null;
			if (folderId.HasValue)
			{
				folder = Data.Get<Folder>(folderId.Value);
				if (folder == null)
					return HttpNotFound("Папка не найдена");
			}
			if (HasFolderListAccessFunc != null && !HasFolderListAccessFunc(folder))
				return HttpAccessDenied("Нет доступа к папке");

			ViewData["Folder"] = folder;
			ViewData["ShowInsertLinks"] = showInsertLinks ?? true;

			ViewData.Model = Data.Query<File>().Where(f => f.Folder == folder)
							.OrderBy(f => f.Name)
							.ThenBy(f => f.Extension)
							.ToList();
			return View();
		}

		/// <summary>
		/// Removes file from database.
		/// </summary>
		/// <param name="id">File id.</param>
		[UrlSubAction("Index")]
		[UrlRoute("{id}/Delete", Path="Admin/Files")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[CmsAuthorize]
		public ActionResult Delete(int id)
		{
			bool isAjax = Request.IsAjaxRequest();

			// get file
			File file = Data.Get<File>(id);
			if (file == null)
				return isAjax ? (ActionResult)Json(true) : RedirectToAction("Index");

			// check access
			int? folderId = file.Folder == null ? (int?)null : file.Folder.Id;
			if (HasFileDeleteAccessFunc != null && !HasFileDeleteAccessFunc(file))
				return isAjax ? (ActionResult)Json(false) : RedirectToAction("Index", new { folderId });
			
			// delete
			using (ITransaction t = Data.BeginTransaction())
			{
				Data.Delete(file);
				t.Commit();
			}
			return isAjax ? (ActionResult)Json(false) : RedirectToAction("Index", new { folderId });
		}

		/// <summary>
		/// Uploads file.
		/// </summary>
		/// <param name="folderId">Identity of the folder to load file to.</param>
		[UrlRoute("Upload", Path="Admin/Files")]
		[CmsAuthorize]
		[ValidateInput(false)]
		public ActionResult Upload(int? folderId, HttpPostedFileBase file)
		{
			if (file == null)
				return RedirectToAction("Index", new { folderId });

			// get folder
			Folder folder = null;
			if (folderId.HasValue)
			{
				folder = Data.Get<Folder>(folderId.Value);
				if (folder == null)
					return null;
			}

			// check access
			if (HasFileUploadAccessFunc != null && !HasFileUploadAccessFunc(folder))
				return null;

			File dbFile = new File(file.FileName);
			dbFile.Folder = folder;
			dbFile.SetData(file.GetBytes());
			using (ITransaction t = Data.BeginTransaction())
			{
				Data.Save(dbFile);
				t.Commit();
			}

			return RedirectToAction("Index", new { folderId });
		}

		#endregion

		//[UrlRoute(RouteIndex = 1, Name = "FilesTable", Path = "Files/{table}-{field}/{keyField}={key}", AddActionToPath = false)]
		//[UrlRoute(RouteIndex = 2, Name = "FilesTableShort", Path = "Files/{table}/{key}", AddActionToPath = false)]
		//public virtual ActionResult FileTable(string table, string key, string field, string keyField)
		//{
		//    // check for null
		//    if (table == null)
		//        throw new ArgumentNullException("table");
		//    else if (key == null)
		//        throw new ArgumentNullException("key");

		//    // set defaults
		//    if (field == null)
		//        field = DefaultFileField;
		//    if (keyField == null)
		//        keyField = DefaultIdField;

		//    // check access
		//    if (!CheckFileAccess(table, field, keyField, key))
		//        return HttpForbidden();

		//    // load data
		//    byte[] data = GetDataFromDb(table, field, keyField, key);
		//    if (data != null)
		//        return File((byte[])data, "application/octet-stream");
		//    return HttpNotFound();
		//}

		//[UrlRoute(RouteIndex = 1, Name = "ImagesSizeTable", Path = "Images/{size}/{table}-{field}/{keyField}={key}", AddActionToPath = false)]
		//[UrlRoute(RouteIndex = 2, Name = "ImagesSizeTableShort", Path = "Images/{size}/{table}/{key}", AddActionToPath = false)]
		//[UrlRouteConstraint(1, "size", Constraints.IsInt)]
		//[UrlRouteConstraint(2, "size", Constraints.IsInt)]

		//[UrlRoute(RouteIndex = 3, Name = "ImagesTable", Path = "Images/{table}-{field}/{keyField}={key}", AddActionToPath = false)]
		//[UrlRoute(RouteIndex = 4, Name = "ImagesTableShort", Path = "Images/{table}/{key}", AddActionToPath = false)]
		//public virtual ActionResult ImageTable(int? size, string table, string key, string field, string keyField)
		//{
		//    // check for null
		//    if (table == null)
		//        throw new ArgumentNullException("table");
		//    else if (key == null)
		//        throw new ArgumentNullException("key");

		//    // set defaults
		//    if (field == null)
		//        field = DefaultImageField;
		//    if (keyField == null)
		//        keyField = DefaultIdField;

		//    // check access
		//    if (!CheckFileAccess(table, field, keyField, key))
		//        return HttpForbidden();

		//    // load image
		//    byte[] data = GetDataFromDb(table, field, keyField, key);
		//    if (data != null && data.Length > 0)
		//    {
		//        System.Drawing.Image image;
		//        var ms = new System.IO.MemoryStream();
		//        ms.Write(data, 0, data.Length);

		//        try
		//        {
		//            image = System.Drawing.Image.FromStream(ms);
		//        }
		//        catch
		//        {
		//            return null;
		//        }

		//        int width = size ?? -1;
		//        int height = size ?? -1;
		//        return Image(image, null, width, height);
		//    }

		//    return HttpNotFound();
		//}

		//[UrlRoute(RouteIndex = 1, Name = "ImagesTablePlain", Path = "PlainImages/{table}-{field}/{keyField}={key}", AddActionToPath = false)]
		//[UrlRoute(RouteIndex = 2, Name = "ImagesTableShortPlain", Path = "PlainImages/{table}/{key}", AddActionToPath = false)]
		//public virtual ActionResult ImageTablePlain(int? size, string table, string key, string field, string keyField)
		//{
		//    // check for null
		//    if (table == null)
		//        throw new ArgumentNullException("table");
		//    else if (key == null)
		//        throw new ArgumentNullException("key");

		//    // set defaults
		//    if (field == null)
		//        field = DefaultImageField;
		//    if (keyField == null)
		//        keyField = DefaultIdField;

		//    // check access
		//    if (!CheckFileAccess(table, field, keyField, key))
		//        return HttpForbidden();

		//    // load image
		//    byte[] data = GetDataFromDb(table, field, keyField, key);
		//    if (data != null && data.Length > 0)
		//    {
		//        System.IO.MemoryStream ms = new System.IO.MemoryStream();
		//        ms.Write(data, 0, data.Length);

		//        string contentType = null;

		//        try
		//        {
		//            Image image = System.Drawing.Image.FromStream(ms);
		//            if (image.RawFormat.Guid == ImageFormat.Png.Guid)
		//                    contentType = "image/png";
		//            else if (image.RawFormat.Guid == ImageFormat.Jpeg.Guid)
		//                    contentType = "image/jpeg";
		//            else if (image.RawFormat.Guid == ImageFormat.Bmp.Guid)
		//                    contentType = "image/bmp";
		//            else if (image.RawFormat.Guid == ImageFormat.Gif.Guid)
		//                    contentType = "image/gif";
		//        }
		//        catch
		//        {
		//            return null;
		//        }

		//        return ImagePlain(data, contentType);
		//    }

		//    return null;
		//}

		///// <summary>
		///// Gets file data from database.
		///// </summary>
		///// <param name="tableName">Table name.</param>
		///// <param name="fieldName">Data field name.</param>
		///// <param name="keyFieldName">Primary key field name.</param>
		///// <param name="key">Primary key value.</param>
		///// <returns>File data or null if not found.</returns>
		//private byte[] GetDataFromDb(string tableName, string fieldName, string keyFieldName, string key)
		//{
		//    // check params
		//    if (String.IsNullOrEmpty(tableName))
		//        throw new ArgumentNullException("tableName");
		//    if (String.IsNullOrEmpty(fieldName))
		//        throw new ArgumentNullException("fieldName");
		//    if (String.IsNullOrEmpty(keyFieldName))
		//        throw new ArgumentNullException("keyFieldName");
		//    if (String.IsNullOrEmpty(key))
		//        throw new ArgumentNullException("key");

		//    if (tableName.Contains(' '))
		//        throw new ArgumentException("Invalid table name.", "tableName");
		//    if (fieldName.Contains(' '))
		//        throw new ArgumentException("Invalid field name.", "fieldName");
		//    if (keyFieldName.Contains(' '))
		//        throw new ArgumentException("Invalid key field name.", "keyFieldName");
		//    if (key.Contains(' '))
		//        throw new ArgumentException("Invalid key.", "key");

		//    // load from db
		//    using (SqlConnection connection = new SqlConnection(CmsConfig.Default.DataProvider.ConnectionData.ConnectionString))
		//    {
		//        SqlCommand sql = connection.CreateCommand();
		//        sql.CommandText = String.Format("SELECT [{0}] FROM {1} WHERE {2} = '{3}'",
		//            fieldName, tableName, keyFieldName, key);

		//        connection.Open();
		//        object o = sql.ExecuteScalar();
		//        return o is byte[] ? (byte[])o : null;
		//    }
		//}
	}
}
